import org.newdawn.slick.*;
import org.newdawn.slick.util.*;
import org.newdawn.slick.gui.*;
import org.newdawn.slick.particles.*;
import org.newdawn.slick.particles.effects.*;
import java.util.*;

public class GameSessionState implements ApplicationState, ComponentListener {

	private static final int CHAT_EXPIRATION_TIME = 10000;
	private static final int CHAT_FADE_TIME = 1000;

	private ApplicationStateManager stateMan;
	private GameContainer container;

	private GameLogic gameLogic;
	private float scrollX;
	private int deltaSum;

	private boolean isHost = false;
	private String localGameName = null; // Will be null if it is single player

	private int time;

	private int verticalRepeat;
	private int horizontalRepeat;
	private int verticalRepeatInterval;
	private int horizontalRepeatInterval;

	private boolean down;
	private boolean left;
	private boolean right;

	private boolean antiLag = true;

	private long ping;
	private long lastPing;

	private HashMap<Integer, Client> clientList = null;
	private ArrayList<Bot> botList = new ArrayList<Bot>();
	//private HashMap<Player, Color> playerColors = new HashMap<Player, Color>();
	//private HashMap<Integer, Color> observerColors = new HashMap<Integer, Color>();
	//private HashMap<Integer, String> observerNames = new HashMap<Integer, String>();

	private LinkedList<String> effects = new LinkedList<String>();

	private ArrayList<ArrayList<String>> statistics = null;
	private ArrayList<Integer>[] graph = null;
	private boolean gameOver;
	private boolean showScoreBoard;

	private boolean isChatting;
	private LobbyTextField chatField;
	private LinkedList<Tuple4<String,String,Long,Color>> chatLines = new LinkedList<Tuple4<String,String,Long,Color>>();

	private ParticleSystem particleSystem;
	private ConfigurableEmitter removeRowEmitter;
	private ConfigurableEmitter fixPieceEmitter;

	private boolean showMinimap = true;

	private Image offImage = null;
	private Graphics off = null;

	/**
	* Constructor for GameSessionState.
	*
	* @param manager The ApplicationStateManager controlling this instance.
	*/
	public GameSessionState(ApplicationStateManager manager, GameContainer container) {
		stateMan = manager;
		this.container = container;
		chatField = new LobbyTextField("", container, Multitris.FONT1, 200, 560, 400, 24, this);
		chatField.addListener(this);
		try {
			particleSystem = new ParticleSystem(new Image("res/particle.png"), 500);
			particleSystem.setRemoveCompletedEmitters(true);
			removeRowEmitter = ParticleIO.loadEmitter("res/effect_removerow.xml");
			fixPieceEmitter = ParticleIO.loadEmitter("res/effect_smoke.xml");
			/*try{
				offImage = new Image(Multitris.BRICK_SIZE*4,Multitris.BRICK_SIZE*4);
				off = offImage.getGraphics();
			} catch(Exception e) {}*/
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	* Renders the MenuState.
	*
	* @param g The graphics context to render to.
	*/
	public void render(Graphics g) {
		Board b = gameLogic.getBoard();
		ArrayList<Player> players = gameLogic.getPlayers();
		Player localPlayer = gameLogic.getPlayerByID(Multitris.clientCommunication.localID);
		Client localClient = clientList.get(Multitris.clientCommunication.localID);

		//render board
		//end render board

		//g.setColor(Color.white);

		/*
		g.setColor(clientList.get(localPlayer.getId()).getColor().darker().darker());
		g.setLineWidth(2);
		g.fillRect(400-8,38,16,16);
		g.setColor(clientList.get(localPlayer.getId()).getColor().darker());
		g.setLineWidth(1);
		g.drawRect(400-8,38,16,16);
		*/

		g.setColor(new Color(0,20,60));
		g.fillRoundRect(400-200,25,180,40,8);
		g.fillRoundRect(400+20,25,180,40,8);

		g.setColor(new Color(0,0,0));
		for(int i=0; i<3; i++) {
			g.setColor(new Color(0,0,40));
			g.fillRect(400-200+20+55*i,30,30,30);
			g.setFont(Multitris.FONT2);
			g.setColor(new Color(150,150,150));
			g.drawString(""+(i+1),400-200+10+55*i,30);
		}
		for(int i=0; i<3; i++) {
			g.setColor(new Color(0,0,40));
			g.fillRect(400+20+20+55*i,30,30,30);
			g.setFont(Multitris.FONT2);
			g.setColor(new Color(150,150,150));
			g.drawString(""+(i+1),400+20+10+55*i,30);
		}

		if (localPlayer != null) {
			if(localPlayer.isPeeking()) {
				//Color color = playerColors.get(localPl);
				for(int off = 0; off < 3 && off<localPlayer.getPeeking(); off++) {
					Piece preview = localPlayer.peekNext(off);
					Brick[][] previewBricks = preview.getBricks();
					for(int x=0; x<4; x++) {
						for(int y=0; y<4; y++) {
							if(previewBricks[x][y] != null) {
								float scale = 3.4f;
								float left = 1+400+20+20+55*off + x*Multitris.BRICK_SIZE/scale;
								float top = 1+30 + (4-y)*Multitris.BRICK_SIZE/scale; // (4-y) to place the piece correctly
								g.scale(1/scale,1/scale);
								g.translate(left*scale,top*scale);
								g.scale(1, -1f); // -1 to flip the image
								//g.setColor(new Color(color.r,color.g,color.b,0.8f));
								//g.fillRect(0,0,Multitris.BRICK_SIZE,Multitris.BRICK_SIZE);
								g.drawImage(Multitris.brickSheet.getSprite(previewBricks[x][y].getEdges(),0),
									0,
									0,
									localClient.getColor());
								g.scale(1, -1f);
								g.translate(-left*scale,-top*scale);
								g.scale(scale,scale);
							}
						}
					}

				}
			}
			int[] powerups = localPlayer.getPowerUps();
			for(int i=0; i<3; i++) {
				if(powerups[i] != 0) {
					float x = 400-200+20+55*i+3;
					float y = 30+3;
					//g.translate(x, y);
					//g.scale(1,-1);
					g.drawImage(Multitris.powerupSheet.getSprite(powerups[i]-1,0),x,y);
					//g.scale(1,-1);
					//g.translate(-x, -y);
				}
			}
		}


		g.setColor(new Color(50,100,200));
		g.setAntiAlias(true);
		g.drawRoundRect(400-200,25,180,40,8);
		g.drawRoundRect(400+20,25,180,40,8);
		for(int i=0; i<3; i++) {
			g.drawRect(400-200+20+55*i,30,30,30);
		}
		for(int i=0; i<3; i++) {
			g.setLineWidth(1);
			g.setColor(new Color(50,100,200));
			g.drawRect(400+20+20+55*i,30,30,30);

			g.setAntiAlias(false);
			if(localPlayer != null && !localPlayer.isPeeking()) {
				g.setLineWidth(4);
				g.setColor(new Color(120,0,0));
				g.drawLine(400+20+20+55*i+5,35,400+20+20+55*i+25,55);
				g.drawLine(400+20+20+55*i+5,55,400+20+20+55*i+25,35);
			}
			g.setAntiAlias(true);
		}

		g.setLineWidth(1);

		g.setAntiAlias(false);

		g.setColor(new Color(150,150,150));
		g.setFont(Multitris.FONT2);
		g.drawString("Powerups",400-215+70,5);
		g.drawString("Upcoming pieces",400+15+55,5);

		if(b.getColumns()*Multitris.BRICK_SIZE > 800)
			g.translate(-(int)scrollX+400-Multitris.BRICK_SIZE,0);
		else
			g.translate(400-(int)(Multitris.BRICK_SIZE*b.getColumns()/2),0);

		/* SWITCH COLORS HERE TO CHANGE BOARD FRAME TO PLAYER COLOR */
		g.setColor(new Color(0,20,60));
		//g.setColor(clientList.get(localPlayer.getId()).getColor().darker().darker());
		g.fillRoundRect(fromBoardCoordinatesX(0)+1-13, 90-10, // x and y
			fromBoardCoordinatesX(b.getColumns()) - fromBoardCoordinatesX(0)+24, // width
			480+20, //height
			10); //radius

		g.setAntiAlias(true);
		g.setColor(new Color(50,100,200));
		//g.setColor(clientList.get(localPlayer.getId()).getColor().darker());
		g.drawRoundRect(fromBoardCoordinatesX(0)+1-13, 90-10, // x and y
			fromBoardCoordinatesX(b.getColumns()) - fromBoardCoordinatesX(0)+24, // width
			480+20, //height
			10); //radius
		g.drawRoundRect(fromBoardCoordinatesX(0)+1-4, 90-1, // x and y
			fromBoardCoordinatesX(b.getColumns()) - fromBoardCoordinatesX(0)+6, // width
			480+2, //height
			5); //radius
		g.setAntiAlias(false);

		g.setColor(new Color(0,0,0));
		g.fillRoundRect(fromBoardCoordinatesX(0)+1-3, 90, // x and y
			fromBoardCoordinatesX(b.getColumns()) - fromBoardCoordinatesX(0)+4, // width
			480, //height
			4); //radius
		for(int i=0; i<b.getColumns(); i+=5) {
			g.drawImage(Multitris.gameSession[Multitris.BACKGROUND_IMAGE], fromBoardCoordinatesX(0+i), 90);
		}

		g.setColor(new Color(40,40,40));
		for(int i=1; i<b.getColumns(); i++) {
			g.drawLine(fromBoardCoordinatesX(i),90+1,fromBoardCoordinatesX(i),90+480);
		}

		g.translate(0, 90+480);
		g.scale(1,-1);
		//g.rotate(180,400,300);

		//g.setColor(new Color(0,0,0));
		//g.fillRect(fromBoardCoordinatesX(0)-1, fromBoardCoordinatesY(b.getRows()), // x and y
		//	fromBoardCoordinatesX(b.getColumns()) - fromBoardCoordinatesX(0)+2, // width
		//	fromBoardCoordinatesY(0) - fromBoardCoordinatesY(b.getRows())); //height
		//g.setColor(new Color(0,0,0));
		//g.fillRoundRect(fromBoardCoordinatesX(0)-2, fromBoardCoordinatesY(b.getRows())-2, // x and y
		//	fromBoardCoordinatesX(b.getColumns()) - fromBoardCoordinatesX(0)+4, // width
		//	fromBoardCoordinatesY(0) - fromBoardCoordinatesX(b.getRows())+4,4); // height

		//for(int i=0; i<playerColors.length; i++) {
		//	g.setColor(playerColors[i]);
		//	g.fillRect(50+i*20,50,20,20);
		//}

		for(int y=0; y<b.getRows(); y++) {
			for(int x=0; x<b.getColumns(); x++) {
				Brick br = b.getBrick(x,y);
				if (br != null) {
					//Color color = playerColors.get(br.getPlayer());
					Client client = clientList.get(br.getPlayer().getId());
					if (br.getPowerup() > 0) {
						g.drawImage(Multitris.powerupSheet.getSprite(br.getPowerup()-1,0).getFlippedCopy(false, true),
							(int)fromBoardCoordinatesX(x),
							(int)fromBoardCoordinatesY(y),
							new Color(255,255,255,100));
					} else {
						Color color = (client == null ? new Color(100, 100, 100) : client.getColor());
						g.drawImage(Multitris.brickSheet.getSprite(br.getEdges(),0),
							(int)fromBoardCoordinatesX(x),
							(int)fromBoardCoordinatesY(y),
							color);
					}
				}
			}
		}


		for(Player pl : players) {
			Piece p = pl.getCurrentPiece();
			Brick[][] bricks = p.getBricks();
			//Color color = playerColors.get(pl);
			Client client = clientList.get(pl.getId());
			float left = fromBoardCoordinatesX(p.getPoint().x + (antiLag ? p.getPseudoPlacement() : 0));
			float top = fromBoardCoordinatesY(p.getPoint().y);
			//off.clearAlphaMap();

			/*int aboveY = (int)(fromBoardCoordinatesY(b.getRows())+15);
			int aboveX = (int)(fromBoardCoordinatesX(p.getPoint().x+1));
			g.setColor(client.getColor().darker());
			g.setLineWidth(2);
			g.setAntiAlias(true);
			//g.drawLine(aboveX,aboveY,aboveX,aboveY+20);
			g.drawOval(aboveX-2,aboveY-2,4,4);
			g.setLineWidth(1);
			g.drawOval(aboveX-4,aboveY-4,8,8);
			g.setAntiAlias(false);*/

			for(int x=0; x<4; x++) {
				for(int y=0; y<4; y++) {
					if(bricks[x][y] != null) {
						if(p.getPoint().y+y < b.getRows()) {
							//float left = fromBoardCoordinatesX(p.getPoint().x + (antiLag ? p.getPseudoPlacement() : 0) + x);
							//float top = fromBoardCoordinatesY(p.getPoint().y + y);
							/*off.drawImage(Multitris.brickSheet.getSprite(bricks[x][y].getEdges(),0),
								(int)fromBoardCoordinatesX(x),
								(int)fromBoardCoordinatesX(y),
								client.getColor());*/
							g.drawImage(Multitris.brickSheet.getSprite(bricks[x][y].getEdges(),0),
								(int)left+(int)fromBoardCoordinatesX(x),
								(int)top+(int)fromBoardCoordinatesX(y),
								(client != null ? client.getColor() : new Color(100, 100, 100)));
							/*if(pl.equals(localPl)) {
								float fade = (50 + (float)(50*Math.sin(Math.toRadians(time/3))))/255f;
								Color c = playerColors.get(localPl);
								g.setColor(new Color(c.r,c.g,c.b,fade));
								g.fillRect((int)left,(int)top,Multitris.BRICK_SIZE,Multitris.BRICK_SIZE);
							}*/
						}
					}
				}
			}
			////g.drawImage(offImage,left-offImage.getWidth()/2,top-offImage.getHeight()/2,left+offImage.getWidth()/2,top+offImage.getHeight()/2,0,0,offImage.getWidth(),offImage.getHeight(),new Color(255,255,255,50));
			//g.drawImage(offImage,left,top,Color.white);
		}

		// Draw minimap

		if (showMinimap) {
			g.resetTransform();

			Board board = gameLogic.getBoard();

			g.translate(800 - 20 - (4*board.getColumns()+2), 110);

			g.setColor(new Color(255,255,255,150));
			g.setAntiAlias(true);
			g.drawRoundRect(-1-2, -1, 4*board.getColumns()+2+4, 4*board.getRows()+2,4);
			g.setAntiAlias(false);

			for (int x=0; x<board.getColumns(); x++) {
				for (int y=0; y<board.getRows(); y++) {
					Brick brick = board.getBrick(x, board.getRows()-y-1);

					if (brick != null) {
						//Client client = clientList.get(brick.getPlayer().getId());
						//g.setColor(client.getColor());
						g.setColor(new Color(255,255,255,60));
						g.fillRect(x*4, y*4, 4, 4);
						g.drawRect(x*4, y*4, 4, 4);
					}
				}
			}
		}


		synchronized (effects) {
			Iterator<String> it = effects.iterator();
			while (it.hasNext()) {
				Scanner s = new Scanner(it.next());
				String type = s.next();
				if (type.equals("removerow")) {
					int y = s.nextInt();
					int playerId = s.nextInt();
					Player player = gameLogic.getPlayerByID(playerId);

					float top = fromBoardCoordinatesY(y);
					/*for (int x=0; x<gameLogic.getBoard().getColumns(); x++) {
						float left = fromBoardCoordinatesX(x);
						ConfigurableEmitter emitter = removeRowEmitter.duplicate();
						emitter.addColorPoint(0.001f, playerColors.get(player));
						emitter.addColorPoint(0.999f, playerColors.get(player));
						particleSystem.addEmitter(emitter);
						emitter.setPosition(left + Multitris.BRICK_SIZE/2, top + Multitris.BRICK_SIZE/2);
						emitter.setEnabled(true);
					}*/
					it.remove();
				}
				if (type.equals("soundremove")) {
					if(Multitris.soundEnabled) {
						int n = s.nextInt();
						if(n < 4)
							Multitris.sound[Multitris.SOUND_REMOVEROW].play(1f,1f);
						else
							Multitris.sound[Multitris.SOUND_REMOVEROW2].play(1f,1f);
					}
					it.remove();
				}
				if (type.equals("fixpiece")) {
					int n = Multitris.SOUND_DROP1;//(int)(Math.random()*(Multitris.SOUND_DROP3-Multitris.SOUND_DROP1+1))+Multitris.SOUND_DROP1;
					if(Multitris.soundEnabled)
						Multitris.sound[n].play(1f,1f);

					int x = s.nextInt();
					int y = s.nextInt();

					float left = fromBoardCoordinatesX(x);
					float top = fromBoardCoordinatesY(y);
					//ConfigurableEmitter emitter = fixPieceEmitter.duplicate();
					//emitter.addColorPoint(0.001f, Color.red);
					//emitter.addColorPoint(0.999f, Color.red);
					//particleSystem.addEmitter(emitter);
					//emitter.setPosition(left + Multitris.BRICK_SIZE/2, top + Multitris.BRICK_SIZE);
					//emitter.setEnabled(true);

					it.remove();
				}
				if (type.equals("nuke")){
					int n= Multitris.SOUND_NUKE;
					if(Multitris.soundEnabled)
						Multitris.sound[n].play(1f,1f);
					it.remove();
				}
				if (type.equals("gravity")){
					int n= Multitris.SOUND_GRAVITY;
					if(Multitris.soundEnabled)
						Multitris.sound[n].play(1f,1f);
					it.remove();
				}
			}
		}

		if (particleSystem != null) {
			particleSystem.render();
		}

		//g.resetTransform();
		if(b.getColumns()*Multitris.BRICK_SIZE > 800)
			g.translate(scrollX,0);

		g.resetTransform();
		if (antiLag) {
			g.setColor(Color.white);
		} else {
			g.setColor(Color.gray);
		}

		int seconds = gameLogic.getPlayedTime() / Multitris.PULSES_PER_SECOND;
		int minutes = seconds/60;
		seconds -= minutes*60;

		g.drawString("F3 to toggle anti-lag",680,50);
		g.setColor(Color.white);
		g.drawString("Ping: "+ping/1000000+"ms",10,30);
		g.drawString("Time: "+(minutes<10?"0":"")+minutes+":"+(seconds<10?"0":"")+seconds,10,70);
		g.drawString("Score: "+gameLogic.getScore(),10,90);
		g.drawString("Level: "+gameLogic.getLevel(),10,110);
		//g.drawString("Holes: "+gameLogic.getBoard().getHoleCount(),10,110);

		if(gameLogic.isPaused()) {
			g.setColor(Color.white);
			g.setFont(Multitris.FONT3);
			g.drawString("Paused ",350,280);
		}
		if(gameOver || showScoreBoard)	{
			g.setColor(new Color(0,20,60,205));
			g.fillRoundRect(100,100,600,400, 20);

			g.setAntiAlias(true);
			g.setColor(new Color(100,100,200));
			g.drawRoundRect(100,100,600,400,20);

			g.setColor(new Color(180,180,180));
			g.setFont(Multitris.FONT2);
			if (gameOver) {
				g.drawString("Press [ESC] to go",580,115);
				g.drawString("to main menu",602,130);
				if (localGameName != null) {
					g.drawString("or write /restart",589,145);
					g.drawString("to restart the game",570,160);
				}
			}

			g.setColor(Color.white);
			g.setFont(Multitris.FONT3);
			g.drawString("Level: ",130,120);
			g.drawString("Score: ",300,120);
			g.drawString("Time: ",130,152);
			g.drawString(""+gameLogic.getLevel(),210,120);
			g.drawString(""+gameLogic.getScore(),380,120);
			g.setFont(Multitris.FONT4);
			g.drawString(""+(minutes<10?"0":"")+minutes+":"+(seconds<10?"0":"")+seconds,210,154);
			/*
			g.drawString("Holes: ",130,150);
			g.drawString(""+gameLogic.getBoard().getHoleCount(),220,150);
			g.setFont(Multitris.FONT2);
			g.drawString("Time played: ",300, 158);
			int seconds = gameLogic.getPlayedTime() / Multitris.PULSES_PER_SECOND;
			int minutes = seconds/60;
			seconds -= minutes*60;
			g.drawString(""+(minutes<10?"0":"")+minutes+":"+(seconds<10?"0":"")+seconds,380,158);
			*/
			int row=0;
			g.setFont(Multitris.FONT2);
			g.drawString("Player name",130,195);
			g.drawString("Rows cleared",130+140,195);
			g.drawString("Pieces placed",130+140*2,195);
			g.drawString("Powerups taken",130+140*3,195);
			for(ArrayList<String> a : statistics) {
				int inner = 0;
				for(String s : a) {
					if(inner == 0) {
						int id = Integer.parseInt(s);
						//Player p = gameLogic.getPlayerByID(id);
						Client client = clientList.get(id);

						if (client != null) { // Make sure the client is known
							g.setColor(Color.black);
							g.drawString(client.getName(),131+140*inner,221+20*row);
							g.drawString(client.getName(),132+140*inner,222+20*row);

							g.setColor(client.getColor()); //playerColors.get(p));
							g.drawString(client.getName(),130+140*inner,220+20*row);
						}
					} else {
						g.setColor(new Color(180,180,180));
						g.drawString(s,130+140*inner,220+20*row);
					}
					inner++;
				}
				row++;
			}
			boolean header = false;
			int xOffset=0;
			for (Client client : clientList.values()) {
				if (client.isObserving()) {
					if (!header) {
						header = true;
						g.setColor(Color.white);
						g.drawString("Observers:",130,460);
					}

					if (xOffset > 0) {
						g.setColor(Color.white);
						g.drawString(",", 200+xOffset-9, 460);
					}

					g.setColor(Color.black);
					g.drawString(client.getName(),201+xOffset,461);
					g.drawString(client.getName(),202+xOffset,462);

					g.setColor(client.getColor()); //playerColors.get(p));
					g.drawString(client.getName(),200+xOffset,460);

					xOffset += Multitris.FONT2.getWidth(client.getName())+10;
				}
			}

			g.setColor(new Color(120, 120, 255));
			g.drawRect(120, 400, 560, 80);
			g.setAntiAlias(true);
			g.setLineWidth(2);
			if (graph[0].size() > 1) {
				float stepWidth = 560.0f/(graph[0].size()-1);

				g.setColor(new Color(120, 255, 120));
				g.drawString("Max height", 125, 405);
				for (int i=0; i<graph[0].size()-1; i++) {
					float ratio1 = getGraphRatio(graph[0], i, gameLogic.getBoard().getRows());
					float ratio2 = getGraphRatio(graph[0], i+1, gameLogic.getBoard().getRows());

					g.drawLine(120 + i*stepWidth, 480 - (int)(80*ratio1),
						120 + (i+1)*stepWidth, 480 - (int)(80*ratio2));
				}
				g.setColor(new Color(200, 180, 120));
				g.drawString("Board coverage", 125, 420);
				for (int i=0; i<graph[1].size()-1; i++) {
					float ratio1 = getGraphRatio(graph[1], i, gameLogic.getBoard().getRows() * gameLogic.getBoard().getColumns());
					float ratio2 = getGraphRatio(graph[1], i+1, gameLogic.getBoard().getRows() * gameLogic.getBoard().getColumns());

					g.drawLine(120 + i*stepWidth, 480 - (int)(80*ratio1),
						120 + (i+1)*stepWidth, 480 - (int)(80*ratio2));
				}
			}
		}
		if(isChatting)
			chatField.render(container,g);

		// render all chat lines
		g.setFont(Multitris.FONT2);
		int lines = 0;
		long currentTime = System.currentTimeMillis();
		Iterator<Tuple4<String,String,Long,Color>> it = chatLines.iterator();
		while(it.hasNext()) {
			Tuple4<String,String,Long,Color> t = it.next();
			long chatTime = t.third;
			if(currentTime-chatTime > CHAT_EXPIRATION_TIME + CHAT_FADE_TIME) {
				it.remove();
			}
		}
		for(Tuple4<String,String,Long,Color> t : chatLines){
			// the : between name and message is inserted directly into the message
			int offset = Multitris.FONT2.getWidth(t.first);
			int transparency = 255-(int)Math.min(255, ((currentTime-t.third - CHAT_EXPIRATION_TIME)*255)/CHAT_FADE_TIME);
			g.setColor(new Color(0, 0, 0, transparency/255.0f));
			g.drawString(t.first,11,531-lines*20);
			g.drawString(t.first,12,532-lines*20);
			g.drawString(t.second,11+0+offset,531-lines*20);
			g.drawString(t.second,12+0+offset,532-lines*20);
			g.setColor(new Color(t.fourth.r, t.fourth.g, t.fourth.b, transparency/255.0f));
			g.drawString(t.first,10,530-lines*20);
			g.setColor(new Color(255,255,255, transparency));
			g.drawString(t.second,10+0+offset,530-lines*20);
			lines++;
		}
	}

	private float getGraphRatio(ArrayList<Integer> list, int index, int max) {
		float sum = 0;
		float times = 0;

		int smooth = 2;

		for (int i=index - smooth; i<index + smooth + 1; i++) {
			if (i >= 0 && i < list.size()) {
				float multi = 1 + (smooth - Math.abs(i - index));
				sum += list.get(i) * multi;
				times += multi;
			}
		}

		if (times > 0) {
			return sum / times / max;
		}
		return sum / max;
	}

	public void deactivateChat() {
		isChatting = false;
	}

	private float toBoardCoordinatesX(float x) {
		return 0;
	}

	private float fromBoardCoordinatesX(float x) {
		return x * Multitris.BRICK_SIZE;
	}

	private float fromBoardCoordinatesY(float y) {
		return y * Multitris.BRICK_SIZE;
		//return (gameLogic.getBoard().getRows()-y) * Multitris.BRICK_SIZE;
	}

	/**
	* Updates the MenuState.
	*
	* @param delta The time passed since the last call to update.
	*/
	public void update(int delta) {
		time += delta;

		particleSystem.update(delta);

		Client localClient = clientList.get(Multitris.clientCommunication.localID);

		if(!gameOver && !localClient.isObserving()) {
			horizontalRepeat += delta;
			verticalRepeat += delta;
			if((left || right) && horizontalRepeat > horizontalRepeatInterval) {
				horizontalRepeatInterval = Math.max(80, horizontalRepeatInterval-30);
				horizontalRepeat = 0;
				String net = null;
				if(left) {
					net = "lft";
					Multitris.clientCommunication.sendAction("cmd "+net);
					gameLogic.pseudoMoveLocal(-1);
				} else if (right) {
					net = "rgt";
					Multitris.clientCommunication.sendAction("cmd "+net);
					gameLogic.pseudoMoveLocal(1);
				}
			}
			if((down) && verticalRepeat > verticalRepeatInterval) {
				verticalRepeatInterval = Math.max(60, verticalRepeatInterval-60);
				verticalRepeat = 0;
				String net = null;
				if(down) {
					net = "dwn";
					Multitris.clientCommunication.sendAction("cmd "+net);
				}
			}
		}
		
		if (!gameOver) {
			// Update all bots at this player
			for (Bot b : botList) {
				b.update(delta);
			}
		}

		String[] strings = Multitris.clientCommunication.getActions();
		for(int i=0; i<strings.length; i++) {
			String s = strings[i];
			if(!gameOver && s.startsWith("pulse")) {
				ArrayList<String> gameActions = new ArrayList<String>();
				String[] pulseStrings = s.split(" ",3);
				int id = Integer.parseInt(pulseStrings[1]);
				int numActions = Integer.parseInt(pulseStrings[2]);
				for(int j=0; j<numActions; j++) {
					i++;
					gameActions.add(strings[i]);
				}
				gameLogic.doActions(gameActions.toArray(new String[0]));
			}
			else if(s.startsWith("pong")) {
				String[] pingStrings = s.split(" ");
				lastPing = Long.parseLong(pingStrings[1]);
				ping = System.nanoTime()-lastPing;
			}
			else if(s.startsWith("playeridswitch")) {
				String[] parts = s.split(" ",3);
				int id = Integer.parseInt(parts[1]);
				int botId = Integer.parseInt(parts[2]);

				Client client = clientList.get(id);
				// Remove observer state for the client that takes over
				if (client != null) {
					client.setObserving(false);
				}
				// Remove the bot that is getting taken over (if that is the case)
				for (Bot b : botList) {
					if (b.getId() == botId) {
						botList.remove(b);
						break;
					}
				}
			}
			else if(s.startsWith("chat")) {
				String[] chatStrings = s.split(" ",3);
				if(chatStrings.length >= 2) {
					//ArrayList<Player> players = gameLogic.getPlayers();
					int id = Integer.parseInt(chatStrings[1]);
					Client client = clientList.get(id);
					String chatStr = ":  " + chatStrings[2];
					Color chatColor = client.getColor(); //Color.white;
					String chatPlayer = client.getName(); //null; //"Default";
					/*for(Player p : players) {
						if(p.getId() == id) {
							//chatStr = ":  " + chatStrings[2];
							chatColor = playerColors.get(p);
							chatPlayer = p.getName();
							break;
						}
					}
					if (chatPlayer == null) {
						chatColor = observerColors.get(id);
						chatPlayer = observerNames.get(id);
					}*/
					chatLines.addFirst(new Tuple4(chatPlayer,chatStr,new Long(System.currentTimeMillis()),chatColor));

					
					// TEMPORARY BOT<->OBSERVER SWITCH CODE
					if (client.isObserving() && id == localClient.getId()) {
						if (chatStrings[2].startsWith("/switch")) {
							String[] params = chatStrings[2].split(" ", 2);
							try {
								int botId = Integer.parseInt(params[1]);
								Client bot = clientList.get(botId);
								if (bot != null && bot.getName().startsWith("@Bot")) {
									Multitris.clientCommunication.sendAction("cmd playeridswitch " + botId);
									Multitris.clientCommunication.sendAction("playeridswitch " + botId);
								}
								/*for (Bot b : botList) {
									if (botId == b.getId()) {
										b.sendAction("chat I'm being replaced by "+ chatPlayer);
										b.remove();
										botList.remove(b);
										break;
									}
								}*/
							} catch (NumberFormatException e) {
							}
						}
					}
				}
			}
			else if (s.startsWith("newclient ")) { // another client joins the server
				String[] params = s.split(" ", 7);
				int id = Integer.parseInt(params[1]);
				Color color = new Color(Float.parseFloat(params[2]), Float.parseFloat(params[3]), Float.parseFloat(params[4]));
				boolean obs = params[5].equals("1");
				String name = params[6];
				//observerNames.put(id, name);
				//observerColors.put(id, color);
				Client newClient = new Client(id, name, color, obs);
				clientList.put(id, newClient);

				//chatLog.add(name+" joined the game.");
				if (newClient.isBot()) {
					addSystemNotification(name+" joined the game to replace a disconnected player.");
				} else {
					addSystemNotification(name+" joined the game as an observer.");
				}

				if (Multitris.clientCommunication.localID == 0) { // host notices a new observer
					// compact whole gamestate (gamelogic), and send it
					if (!newClient.isBot()) { // client is not a bot
						System.out.println("Packing gamestate to a string.");
						long time = System.nanoTime();
						String gameState = gameLogic.getGameState();
						long diff = (System.nanoTime()-time)/1000000;
						System.out.println("Time to save: "+diff+"ms");
						System.out.println("Gamestate size: "+gameState.length());
						System.out.println("Sending gamestate to an observer.");
						time = System.nanoTime();
						Multitris.clientCommunication.sendAction("state "+id+" "+gameState);
						diff = (System.nanoTime()-time)/1000000;
						System.out.println("Time to send: "+diff+"ms");
					}
				}
			}
			else if (s.startsWith("clientdisconnected")) {
				String[] chatStrings = s.split(" ",2);
				if(chatStrings.length >= 2) {
					//ArrayList<Player> players = gameLogic.getPlayers();
					int id = Integer.parseInt(chatStrings[1]);
					Client client = clientList.get(id);
					client.setDisconnected(true);
					String chatStr = client.getName() + (client.isObserving() ? " (observer)" : "") + " disconnected from the game!";
					Player p = gameLogic.getPlayerByID(id);
					if (p != null) {
						p.disable();
					}

					if (localClient.getId() == 0) { // is host
						if (!client.isObserving() && !client.isBot() && !gameOver) {
							// dropped client was not a bot, and was not observing
							// replace dropped client with a bot
							Bot b = new Bot("localhost", id);
							b.setGameLogic(gameLogic);
							botList.add(b);
						}
					}
					/*for(Player p : players) {
						if(p.getId() == id) {
							chatStr = p.getName() + " disconnected from the game!";
							gameLogic.disablePlayer(p);
							break;
						}
					}
					if (chatStr.equals("")) {
						chatStr = observerNames.get(id) + " (observer) disconnected from the game!";
					}*/
					//chatLines.addFirst(new Tuple4("System",":  "+chatStr,new Long(System.currentTimeMillis()),Color.white));
					if (client.isObserving()) {
						clientList.remove(id);
					}
					addSystemNotification(chatStr);
				}
			}
			else if (s.startsWith("servershutdown")) {
				String[] parts = s.split(" ", 2);
				if (parts.length == 1) {
					gameOver(gameLogic.getStatistics(), gameLogic.getGraph());
					addSystemNotification("The server have been shut down!");
				} else {
					// Reconnect to the server

				}
			}
			else if (s.startsWith("rematch")) {
				ArrayList<Object> par = new ArrayList<Object>();
				par.add(ChatLobbyState.REMATCH);
				par.add(clientList);
				par.add(botList);
				par.add(localGameName);
				stateMan.switchState(ApplicationStateManager.CHAT, par);
			}
		}

		long currentTime = System.currentTimeMillis();
		Iterator<Tuple4<String,String,Long,Color>> it = chatLines.iterator();
		while(it.hasNext()) {
			Tuple4<String,String,Long,Color> t = it.next();
			long chatTime = t.third;
			if(currentTime-chatTime > CHAT_EXPIRATION_TIME + CHAT_FADE_TIME) {
				it.remove();
			}
		}

		deltaSum+=delta;
		if(deltaSum > 2000) {
			deltaSum = 0;
			//lastPing = System.nanoTime();
			Multitris.clientCommunication.sendAction("ping "+System.nanoTime());
		}

		//ArrayList<Player> players = gameLogic.getPlayers();
		//for(Player pl : players) {
		//	if(pl.getId() == Multitris.clientCommunication.localID) {
		Player localPlayer = gameLogic.getPlayerByID(Multitris.clientCommunication.localID);
		if (localPlayer != null) {
			float curx = fromBoardCoordinatesX(localPlayer.getCurrentPiece().getPoint().x);
			//scrollX += (scrollx-curx)*0.98-800/2;
			scrollX = (scrollX * 99.3f + curx * 0.7f)/100f;//-800/2;
			//scrollX += (float)(Math.signum(scrollX-curx)*Math.pow(Math.abs(scrollX-curx),1.3));
			if (scrollX < 330) scrollX = 330;
			if (scrollX > fromBoardCoordinatesX(gameLogic.width) - 370) scrollX = fromBoardCoordinatesX(gameLogic.width) - 370;

			//System.out.println(scrollX + " - " + fromBoardCoordinatesX(gameLogic.width));
		}
		if (localClient.isObserving()) {
			// Observer

			if (left) {
				scrollX -= delta/2.5f;
			}
			if (right) {
				scrollX += delta/2.5f;
			}
			if (scrollX < 330) scrollX = 330;
			if (scrollX > fromBoardCoordinatesX(gameLogic.width) - 370) scrollX = fromBoardCoordinatesX(gameLogic.width) - 370;
		}
	}


	/**
	* Sets a mode for the state.
	*
	* @param parameter the mode to set.
	*/
	public void setMode(Object parameter) {
		scrollX = 0;
		deltaSum = 0;
		time = 0;
		verticalRepeat = 0;
		horizontalRepeat = 0;
		verticalRepeatInterval = 0;
		horizontalRepeatInterval = 0;
		down = false;
		left = false;
		right = false;

		ping = 0;
		lastPing = 0;

		clientList = null;
		effects.clear();
		statistics = null;
		gameOver = false;
		showScoreBoard = false;
		isChatting = false;
		chatLines.clear();

		showMinimap = true;
		offImage = null;
		off = null;


		ArrayList<Object> parameters = (ArrayList<Object>)parameter;
		//ArrayList<String> playerNames = (ArrayList<String>)parameters.get(0);
		//ArrayList<Integer> playerIDs = (ArrayList<Integer>)parameters.get(1);
		//ArrayList<Color> colors = (ArrayList<Color>)parameters.get(2);
		//Integer seed = (Integer)parameters.get(3);

		if (parameters.size() == 5 || parameters.size() == 2) { // real player, 5 if multiplayer, 2 if singleplayer
			clientList = (HashMap<Integer, Client>)parameters.get(0);
			Integer seed = (Integer)parameters.get(1);
			if (parameters.size() == 5) { // Multiplayer
				isHost = (Boolean)parameters.get(2);
				localGameName = (String)parameters.get(3);
				botList = (ArrayList<Bot>)parameters.get(4); // list of bots hosted by local player
			} else { // Singleplayer
				isHost = true;
				localGameName = null;
			}

			ArrayList<Integer> playerIds = new ArrayList<Integer>();
			//clientIds.addAll(clientList.keySet());
			for (Client client : clientList.values()) {
				if (!client.isObserving()) {
					playerIds.add(client.getId());
				}
			}

			Collections.sort(playerIds);

			gameLogic = new GameLogic(playerIds, seed, this); //playerNames, playerIDs, seed, this);

			// Give all local bots a reference to the gamelogic
			for (Bot b : botList) {
				b.setGameLogic(gameLogic);
			}
		} else if (parameters.size() == 3) { // observer
			clientList = (HashMap<Integer, Client>)parameters.get(0);
			isHost = false;

			String data = (String)parameters.get(2);
			gameLogic = GameLogic.fromSerialization(this, (ArrayList<Tuple2<Integer, String[]>>)parameters.get(1), data);
		}

		/*int i=0;
		for (Player p : gameLogic.getPlayers()) {
			playerColors.put(p, colors.get(i));
			/*
			if (i >= PLAYER_COLORS.length) {
				Color c = new Color((float)Math.random(), (float)Math.random(), (float)Math.random());
			} else {
				playerColors.put(p, PLAYER_COLORS[i]);
			}* /

			i++;
		}*/

		scrollX = 400;

		Multitris.currentMusic.stop();
		Multitris.currentMusic = Multitris.music[Multitris.MUSIC_GAME];
		Multitris.currentMusic.loop();
		Multitris.currentMusic.setVolume(0.4f);
		if(!Multitris.musicEnabled) {
			Multitris.currentMusic.pause();
		}
		gameOver = false;
	}

	public void componentActivated(AbstractComponent object) {
		if(object == chatField) {
			String str = chatField.getText();
			chatField.setText("");
			if (str.startsWith("/rematch") || str.startsWith("/restart")) {
				Multitris.clientCommunication.sendAction("rematch");
			} else {
				if(!str.equals(""))
					Multitris.clientCommunication.sendAction("chat "+str);
				isChatting = false;
				chatField.setFocus(false);
			}
		}
	}

	/**
	* Informs the GameSessionState that an action has been requested.
	*
	* @param action The action requested.
	*/
	public void doInputAction(String action) {
		String net = null;

		Client localClient = clientList.get(Multitris.clientCommunication.localID);

		if(action.equals("p_tab") || action.equals("p_scoreboard")) {
			statistics = gameLogic.getStatistics();
			graph = gameLogic.getGraph();
			showScoreBoard = true;
		}
		if(action.equals("r_tab") || action.equals("r_scoreboard")) {
			showScoreBoard = false;
		}

		if(action.equals("p_use1")) {
			isChatting = true;
			chatField.setFocus(true);
		}

		if (action.equals("p_cancel") && (localClient.isObserving() || gameOver || gameLogic.getPlayers().size() == 1)) {
			//Multitris.clientCommunication.sendAction("disconnect");
			Multitris.clientCommunication.disconnectFromGameServer();
			stateMan.switchState(ApplicationStateManager.MENU);
			stateMan.getCurrentState().setMode(null);
		}

		if(gameOver)
			return;

		if (localClient.isObserving()) {
			if (action.equals("p_left")) {
				horizontalRepeatInterval=150;
				left = true;
			}
			if (action.equals("p_right")) {
				horizontalRepeatInterval=150;
				right = true;
			}
			if (action.equals("p_down") || action.equals("p_use2")) {
				verticalRepeatInterval=140;
				down = true;
			}

			if(action.equals("r_left")){
				left = false;
			}
			if(action.equals("r_right")){
				right = false;
			}
			if(action.equals("r_down") || action.equals("r_use2")){
				down = false;
			}
		} else {
			if (action.equals("p_left")) {
				horizontalRepeatInterval=150;
				left = true;
				net = "lft";
			}
			if (action.equals("p_right")) {
				horizontalRepeatInterval=150;
				right = true;
				net = "rgt";
			}
			if (action.equals("p_down") || action.equals("p_use2")) {
				verticalRepeatInterval=140;
				down = true;
				net = "dwn";
			}
			if (action.equals("p_ccw")) {
				Multitris.clientCommunication.sendAction("cmd ccw");
			}
			if (action.equals("p_up")) {
				Multitris.clientCommunication.sendAction("cmd _cw");
			}
			if (action.equals("p_pause")) {
				Multitris.clientCommunication.sendAction("cmd pause");
			}

			if(action.equals("r_left")){
				left = false;
			}
			if(action.equals("r_right")){
				right = false;
			}
			if(action.equals("r_down") || action.equals("r_use2")){
				down = false;
			}

			if (action.equals("p_antilag")) {
				antiLag = !antiLag;
			}

			if ((action.equals("p_right") || action.equals("p_left")) && horizontalRepeat > horizontalRepeatInterval) {
				if (net != null) {
					horizontalRepeat = 0;
					Multitris.clientCommunication.sendAction("cmd "+net);
					if (net.equals("lft")) {
						gameLogic.pseudoMoveLocal(-1);
					} else {
						gameLogic.pseudoMoveLocal(1);
					}
				}
			}
			if ((action.equals("p_down")) && verticalRepeat > verticalRepeatInterval) {
				if (net != null) {
					verticalRepeat = 0;
					Multitris.clientCommunication.sendAction("cmd "+net);
				}
			}
			if (action.equals("p_num1")) {
				Multitris.clientCommunication.sendAction("cmd pwr1");
			}
			if (action.equals("p_num2")) {
				Multitris.clientCommunication.sendAction("cmd pwr2");
			}
			if (action.equals("p_num3")) {
				Multitris.clientCommunication.sendAction("cmd pwr3");
			}
		}
	}

	/**
	* Adds a new effect to the effect queue.
	*
	* @param data data for the effect to be added, can be anything
	*/
	public void addEffect(String data) {
		synchronized (effects) {
			effects.addLast(data);
		}
	}

	public void addActionNotification(Client client, String message) {
		chatLines.addFirst(new Tuple4(client.getName(), message, new Long(System.currentTimeMillis()), client.getColor()));
	}

	public void addActionNotification(int id, String message) {
		addActionNotification(clientList.get(id), message);
	}

	public void addActionNotification(Player player, String message) {
		addActionNotification(player.getId(), message);
	}

	public void addSystemNotification(String message) {
		chatLines.addFirst(new Tuple4("System", ": "+message, new Long(System.currentTimeMillis()), Color.white));
	}

	/**
	* Informs the GameSessionState that the game is over.
	*
	* @param score The final score in the game.
	* @param stats The statistics for the game.
	*/
	public void gameOver(ArrayList<ArrayList<String>> stats, ArrayList<Integer>[] graph) {
		statistics = stats;
		this.graph = graph;
		gameOver = true;
		ArrayList<String> players = new ArrayList<String>();
		int i = 0;
		for(Client c : clientList.values()) {
			if(!c.isObserving()) {
				players.add(c.getName());
				i++;
			}
		}

		if (isHost && localGameName != null) {
			Multitris.clientCommunication.sendScoreToCentralServer(localGameName, players, gameLogic.getScore(), gameLogic.getLevel(), gameLogic.getPlayedTime()/Multitris.PULSES_PER_SECOND);
		}
	}

	/**
	* Informs the MenuState that the mouse has been moved.
	*
	* @param oldx The old x coordinate.
	* @param oldy The old y coordinate.
	* @param newx The new x coordinate.
	* @param newy The new y coordinate.
	*/
	public void mouseMoved(int oldx, int oldy, int newx, int newy) {
	}

	/**
	* Informs the MenuState that a mouse button has been pressed.
	*
	* @param button The old mouse button pressed.
	* @param y The y coordinate.
	* @param x The x oordinate.
	*/
	public void mousePressed(int button, int x, int y) {
	}
}